# = StarFrame::Sprite::Animatable::Animation -- スプライトアニメーション定義

module StarFrame
  class Sprite
    module Animatable
      # = Sprite::Animatable::Animationクラス
      # スプライトアニメーションクラス。
      # 
      # アニメーションを実行するクラス。
      class Animation
        def initialize target, max_frame, animations, options
          @target = target
          @max_frame = max_frame-1
          
          @animations = animations.sort
          @animations << [-1]
          
          @options = {:loop => true, :start => true}
          @options.merge!(options)
          @running = !!@options.delete(:start)
          
          rewind
        end
        
        def update
          return unless @running
          
          if @frame == @max_frame
            rewind
            return stop unless @options[:loop]
          end
          @frame += 1
          if @frame == @animations[@index][0]
            @animations[@index][1].each do |method, c|
              @target.__send__ method, c
            end
            @index += 1
          end
        end
        
        # アニメーションを開始。
        def start
          @running = true
        end
        
        # アニメーションを停止。
        def stop
          @running = false
        end
        
        # アニメーションフレームを先頭に戻す。
        # 
        # フレームを戻すだけなので、先頭のアニメーションを自動的に実行するわけではありません。
        def rewind
          @frame = -1
          @index = 0
        end
        
        # アニメーションを再起動。
        def restart
          rewind
          start
        end
        
        # = Sprite::Animatable::Animation::Builderクラス
        # スプライトアニメーション定義クラス。
        # 
        # アニメーションの定義を行うクラス。
        class Builder
          # アニメーションを作成する
          # 
          # ブロックが渡された場合、直列モードでブロックを実行する。
          def initialize target, name, options = {}, &block
            @target, @name = target, name
            
            @options = {:loop => true, :start => true}
            @options.merge!(options)
            
            @animations = Hash.new{ |hash, key| hash[key] = [] }
            @max_frame = 0
            @method = 0
            
            serial &block if block
          end
          
          # 直列モードでブロックを実行する。
          def serial &animation_block
            if @mode == :parallel
              @max_frame = @parallel_start_frame
            end
            
            block :serial, &animation_block
            
            if @mode == :parallel
              @parallel_max_frame = @max_frame if @max_frame > @parallel_max_frame
            end
          end
          
          # 並列モードでブロックを実行する。
          def parallel &animation_block
            last_parallel_start_frame = @parallel_start_frame
            @parallel_start_frame = @max_frame
            
            @parallel_max_frame = 0
            block :parallel, &animation_block
            
            @max_frame = @parallel_max_frame
            @parallel_start_frame = last_parallel_start_frame
          end
          
          # アニメーションフレームを複数追加する。
          # 
          # params
          #   coefficients - 各コマでブロックに渡す値の列挙（ArrayやRangeなど）。
          #   interval - コマ間隔フレーム数（正の整数）。1で毎フレーム実行される。
          #   &block - 各コマで実行されるブロック。引数にはそのコマのcoefficientsの値が渡される。
          def frames coefficients, interval = 1, &block
            raise ArgumentError, "animate interval can't be 0"        if interval == 0
            raise ArgumentError, "animate interval can't be negative" if interval < 0
            
            if coefficients.kind_of? Numeric
              coefficients = 0...coefficients.to_i
            end
            raise ArgumentError if coefficients.to_a.size.zero?
            
            case @mode
            when :serial
              frames_logic coefficients, interval, &block
            else
              serial do
                frames_logic coefficients, interval, &block
              end
            end
          end
          # アニメーションフレームを追加する。
          # 
          # params
          #   interval - 次のコマまでの間隔（正の整数）
          def frame interval = 1, &block
            frames 1, interval, &block
          end
          
          # 空のアニメーションフレームを追加する。
          # 
          # params
          #   frame - 停止する長さ（正の整数）
          def wait frame
            @max_frame += frame
          end
          
          # アニメーションを構築する。
          def build target
            Animation.new target, @max_frame, @animations, @options
          end
          
          private
          def block mode, &block
            last_mode = @mode
            @mode = mode
            self.instance_eval &block
            @mode = last_mode
          end
          
          def frames_logic coefficients, interval = 1, &block
            @method += 1
            method_name = "_starframe_sprite_animatable_animation__animation_#{@name}_frames_#{@method}".to_sym
            
            @target.__send__ :define_method, method_name, &block
            
            frame = @max_frame
            coefficients.each do |c|
              @animations[frame] << [method_name, c]
              frame += interval
            end
            @max_frame = frame
          end
        end
      end
    end
  end
end
